/* eslint-disable no-bitwise */
/**
 * 默认报告样式
 *
 * Created by snowingsea on 2021/01/19.
 */
const fs = require('fs-extra');
const path = require('path');
const moment = require('moment');

const files = require('./../../../files');
const functions = require('../../../functions');
const { coverageKind, mcdcKind } = require('./../../../tests');
const utility = require('./../../../utility');
const ParentReporter = require('./../default-report/index');
const ProjectVersionSpliter = require('./../../project-version-spliter');
const DataAdapter = require('./data-adapter');
const { collectCoverageKind, versionType } = require('../../../project-versions');
const {
  generateStatemetBlocks,
  generateBranchBlocks,
  generateConditionBlocks,
  generateDecisionBlocks,
  generateMcDcBlocks,
  generateCondMcDcBlocks,
} = require('./../functionUtil');

class DefaultReporter extends ParentReporter {
  constructor(
    versionId,
    locale,
    version = null,
    project = null,
    creator = null,
    outputDir = null,
    format,
    complete,
    reportRange = null,
    includeTestcases = false
  ) {
    super(versionId, locale, version, project, creator, outputDir, format, complete, reportRange, includeTestcases);
    this.baseDir = __dirname;
    this.version.versionType = versionType.unitTest;
  }

  setTargetMachineDataSource(data) {
    this.setDataAdapter(new DataAdapter(data.filesMap, data.functionsMap, data.testcasesMap, this.version, this.project));
  }

  getReportName() {
    return '软件测试报告';
  }

  /**
   * 生成测试报告总览
   * @returns {Promise}
   */
  generateSummary() {
    const summary = {
      reportTime: moment().format('YYYY/MM/DD HH:mm:ss'),
      isIntegrationTest: this.isIntegrationTest(),
      reportName: this.getReportName(),
      coverageRange: !this.version.collectCoverageKind ? collectCoverageKind.all : this.version.collectCoverageKind,
      headerRightContent: `${this.getReportName()} - 总览信息`,
    };
    const extensions = Object.values(this.version.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    return this.getDataAdapter().getVersionOverview(this.versionId)
      .then((overview) => {
        const enabledCoverageKind = [];
        ['statement', 'branch', 'condition', 'decision', 'mcdc', 'invoke', 'invokedFunc'].forEach((key) => {
          if (this.version.coverages & coverageKind[key]) {
            const o = {
              key,
              coverageKey: `${key}Coverage`,
              totalKey: `${key}Total`,
              coveredKey: `${key}Covered`,
              name: this.i18n.__(`tests.${key}Coverage`),
            };
            enabledCoverageKind.push(o);
          }
          overview[`${key}Coverage`] = '-';
          if (overview[`${key}Total`]) {
            overview[`${key}Coverage`] = overview[`${key}Covered`] / overview[`${key}Total`];
            overview[`${key}Coverage`] = `${Math.round(overview[`${key}Coverage`] * 10000) / 100}%`;
          }
        });
        summary.overview = overview;
        summary.enabledCoverageKind = enabledCoverageKind;
        return this.getDataAdapter().getFilesOverview(this.versionId, extensions);
      })
      .then((filesOverview) => {
        summary.sourceFilesOverview = ParentReporter.handleOverview(
          filesOverview,
          summary.enabledCoverageKind
        );
        return this.getDataAdapter().getFunctionsOverview();
      })
      .then((functionsOverview) => {
        summary.functionsOverview = ParentReporter.handleOverview(
          functionsOverview,
          summary.enabledCoverageKind
        );
        return this.getDataAdapter().getFunctionCoverageDistribution(this.versionId);
      })
      .then((functionsCoverage) => {
        summary.functionsCoverage = functionsCoverage;
        summary.functionsCoverage.interval = 25;
        return Promise.resolve();
      })
      .then(() => this.generateCoverageDistributionImage(
        summary.functionsCoverage,
        this.getImageSize().coverageDistributionImage
      ))
      .then((coverageDistributionImage) => {
        summary.coverageDistributionImage = coverageDistributionImage.output;
        return this.generateSummaryReport(
          summary,
          path.join(
            this.outputDir,
            this.project.projectName,
            this.version.versionName,
            'summary'
          )
        )
          .then(() => fs.remove(coverageDistributionImage.output));
      });
  }

  async handleFile(file, versionDir) {
    const reportPath = `${path.join(versionDir, path.dirname(file.path), file.fileName)}`;
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions: funcs } = res;
    const fileContent = await files.getLinesOfRemoteFileStream(file._id, {});
    const linesInSourceFile = fileContent.map(({ content }) => content);
    const seniorTestIds = ['1'];
    let juniorTestIds = [];
    await utility.arrayChunkOperation(funcs, 10, async (func) => {
      func.functionCfg = await this.getDataAdapter().getFunctionCfg(func);
      if (func.testcasesCoveredFields) {
        juniorTestIds = Object.keys(func.testcasesCoveredFields);
      }
      let testIdGroups = [];
      if (this.version.mode === 'mixed') {
        testIdGroups = [seniorTestIds, juniorTestIds];
      }
      if (this.willTestStatements()) {
        generateStatemetBlocks([func], linesInSourceFile, testIdGroups);
      }
      if (this.willTestBranch()) {
        generateBranchBlocks([func], linesInSourceFile, testIdGroups);
      }
      if (this.willTestCondition()) {
        generateConditionBlocks([func], testIdGroups);
      }
      if (this.willTestDecision()) {
        generateDecisionBlocks([func], linesInSourceFile, testIdGroups);
      }
      if (this.willTestMcdc()) {
        // 算法一
        if (this.version.mcdcKind === mcdcKind.masking) {
          generateMcDcBlocks([func], testIdGroups);
          // 算法二
        } else {
          generateCondMcDcBlocks([func], testIdGroups);
        }
      }
      const groupCoverageInfo = {
        senior: {},
        junior: {},
        total: {
          statementCovered: func.statementCovered,
          statementTotal: func.statementTotal,
          branchCovered: func.branchCovered,
          branchTotal: func.branchTotal,
          conditionCovered: func.conditionCovered,
          conditionTotal: func.conditionTotal,
          decisionCovered: func.decisionCovered,
          decisionTotal: func.decisionTotal,
          mcdcCovered: func.mcdcCovered,
          mcdcTotal: func.mcdcTotal,
          invokeCovered: func.invokeCovered,
          invokeTotal: func.invokeTotal,
          invokedFuncCovered: func.invokedFuncCovered,
          invokedFuncTotal: func.invokedFuncTotal,
        },
      };
      const funcPureCfg = await this.getDataAdapter().getFunctionCfg(func, false);
      [['senior', func.coveredFields], ['junior', func.testcasesCoveredFields]].forEach(([key, covereds]) => {
        const funcPureCfgCopy = JSON.parse(JSON.stringify(funcPureCfg));
        if (key === 'senior') {
          functions.mergeCfgCovered(funcPureCfgCopy, covereds);
        } else {
          Object.keys(covereds).forEach((testId) => {
            functions.mergeCfgCovered(funcPureCfgCopy, covereds[testId], testId);
          });
        }
        if (this.version.mcdcKind === mcdcKind.masking) {
          functions.completeDecisionCases(funcPureCfgCopy, this.version.mcdcKind);
          functions.mergeDecisionCasesTestIds(funcPureCfgCopy);
        }
        const {
          statementCovered,
          statementTotal,
          branchCovered,
          branchTotal,
          conditionCovered,
          conditionTotal,
          decisionCovered,
          decisionTotal,
          mcdcCovered,
          mcdcTotal,
        } = functions.getCoverage(funcPureCfgCopy, this.version.mcdcKind);
        groupCoverageInfo[key].statementCovered = statementCovered;
        groupCoverageInfo[key].statementTotal = statementTotal;
        groupCoverageInfo[key].branchCovered = branchCovered;
        groupCoverageInfo[key].branchTotal = branchTotal;
        groupCoverageInfo[key].conditionCovered = conditionCovered;
        groupCoverageInfo[key].conditionTotal = conditionTotal;
        groupCoverageInfo[key].decisionTotal = decisionTotal;
        groupCoverageInfo[key].decisionCovered = decisionCovered;
        groupCoverageInfo[key].mcdcCovered = mcdcCovered;
        groupCoverageInfo[key].mcdcTotal = mcdcTotal;
        groupCoverageInfo[key].invokeCovered = 0;
        groupCoverageInfo[key].invokeTotal = 0;
        groupCoverageInfo[key].invokedFuncCovered = 0;
        groupCoverageInfo[key].invokedFuncTotal = 0;
        if (key !== 'senior') {
          groupCoverageInfo[key].invokeCovered = func.invokeCovered;
          groupCoverageInfo[key].invokeTotal = func.invokeTotal;
          groupCoverageInfo[key].invokedFuncCovered = func.invokedFuncCovered;
          groupCoverageInfo[key].invokedFuncTotal = func.invokedFuncTotal;
        }
      });
      func.coverageInfo = groupCoverageInfo;
    });

    funcs.map((func) => {
      func.codeCoverageDetails = [];
      if (this.willTestStatements()) {
        func.codeCoverageDetails.push(func.statementsBlocks);
      }
      if (this.willTestBranch() && func.branchTotal > 0) {
        func.codeCoverageDetails.push(func.branchBlocks);
      }
      if (this.willTestCondition() && func.conditionTotal > 0) {
        func.codeCoverageDetails.push(func.conditionsBlocks);
      }
      if (this.willTestDecision() && func.decisionTotal > 0) {
        func.codeCoverageDetails.push(func.decisionBlocks);
      }
      if (this.willTestMcdc() && func.mcdcTotal > 0) {
        func.codeCoverageDetails.push(func.mcdcBlocks || func.condMcdcBlocks);
      }
      return func;
    });
    await fs.ensureDir(path.dirname(reportPath));
    await this.generateFileReport(
      {
        fileName: file.fileName,
        summary: { functionCount: file.functionCount },
        functions: this.getFunctionDetailsOfFile(typeSystem, funcs),
        isIntegrationTest: this.isIntegrationTest(),
        reportName: this.getReportName(),
        headerRightContent: `${this.getReportName()} - ${file.fileName}`,
      },
      reportPath
    );
    if (this.includeTestcases) {
      await this.generateTestcaseFiles(file, funcs, versionDir);
    }
    return { reportPath };
  }

  /**
   * 获取函数列表详细信息
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数列表
   * @returns {Array}
   */
  getFunctionDetailsOfFile(typeSystem, functions) {
    const resFunctions = [];
    // 低层高层混合模式
    const mixed = this.version.mode === 'mixed';
    functions.forEach((func) => {
      const data = {
        functionName: func.functionName,
        summary: this.getSummaryOfFunction(func),
        mixed,
      };
      if (mixed && func.tests) {
        func.tests.forEach((test) => {
          const key = DefaultReporter.getTestResultKindKey(test.resultKind);
          test.resultKindName = key;
        });
        data.tests = this.getTestDetailsOfFunction(typeSystem, func);
      }
      resFunctions.push(data);
    });
    return resFunctions;
  }

  /**
   * 获取某个函数的概要统计
   * @param {Object}} func 函数信息
   * @returns {Object}
   */
  getSummaryOfFunction(func) {
    const coverageArr = [];
    const { coverages } = this.version;
    ['statement', 'branch', 'condition', 'decision', 'mcdc'].forEach((kind) => {
      const summary = {};
      ['senior', 'junior', 'total'].forEach((type) => {
        summary[type] = {};
        // eslint-disable-next-line no-bitwise
        if (func.coverageInfo[type][`${kind}Total`] && (coverages & coverageKind[kind]) > 0) {
          summary[type].value = func.coverageInfo[type][`${kind}Covered`] / func.coverageInfo[type][`${kind}Total`];
          summary[type].value = `${Math.round(summary[type].value * 10000) / 100}%`;
        } else if ((coverages & coverageKind[kind]) > 0) {
          summary[type].value = '-';
        }
      });
      if (summary.total.value) {
        summary.name = this.i18n.__(`tests.${kind}CoverageRate`);
        coverageArr.push(summary);
      }
    });
    return { coverages: coverageArr, codeCoverageDetails: func.codeCoverageDetails };
  }
}

/**
 * Export model definition object.
 */
module.exports = DefaultReporter;
